#include <string>
#include <iostream>
#include <filesystem>
#include <fstream>
#include <cmath>
#include <cstdio>
#include <memory>
#include <stdexcept>
#include <array>
#include <iomanip>

#include "rocksdb/db.h"
#include "rocksdb/options.h"

using namespace rocksdb;
using namespace std;
namespace fs = filesystem;


fs::path data_path = "./dataset/DataSet/enronMail";
fs::path database_path = "./rocksdb-enron";

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

int get_total_emails(void){
    string cmd = "find " + data_path.string() + " | wc -l";
    string res = exec(cmd.c_str());
    return stoi(res);
}


struct HumanReadable {
    uintmax_t size{};
  private: friend
    ostream& operator<<(ostream& os, HumanReadable hr) {
        int i{};
        double mantissa = hr.size;
        for (; mantissa >= 1024.; mantissa /= 1024., ++i) { }
        mantissa = ceil(mantissa * 10.) / 10.;
        os << mantissa << "BKMGTPE"[i];
        return i == 0 ? os : os << "B (" << hr.size << ')';
    }
};

int main()
{
    DB *db;
    Options options;

    // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
    options.IncreaseParallelism();
    options.OptimizeLevelStyleCompaction();
    // create the DB if it's not already present
    options.create_if_missing = true;

    // open DB
    Status s = DB::Open(options, database_path.string(), &db);
    assert(s.ok());
    

    // iterate data path for all files in data_path
    // fs:current_path(data_path);
    cout << "please wait, this may takes a few minutes" << endl;
    int tot = get_total_emails();
    double pre_per = 0;
    int cnt = 0;
    struct HumanReadable tot_size;
    for (fs::recursive_directory_iterator file(data_path), end; file != end; ++file) 
        if (!is_directory(file->path())){
            string path = file->path().string();
            string filename = file->path().filename().string();
            // cout << "find file:" << path << endl;
            ifstream fin(path);

            string keyname, key;

            // key is the first line of the email, for example: 
            // Message-ID: <18782981.1075855378110.JavaMail.evans@thyme>
            // so keyname = "Message-ID:", key = "<18782981.1075855378110.JavaMail.evans@thyme>"
            fin >> keyname >> key;

            // read whole e-mail as value
            fin.seekg(0);
            stringstream buffer;
            buffer << fin.rdbuf();
            string content = buffer.str();
            
            
            s = db->Put(WriteOptions(), key, content);
            if (!s.ok()) cerr << s.ToString() << endl;
            else{
                tot_size.size += key.size() + content.size();
                ++cnt;
                double now_per = (double)cnt/tot;
                if(now_per - pre_per > 0.01){
                    cout << setprecision(2) << now_per*100 << "% done" << endl;
                    pre_per = now_per;
                }
            }
            
        }
    cout << cnt << " emails have been insert into rocks databse! " << endl;
    cout << "The altogether size of keys and values are " << tot_size << endl;
    cout << "rocks database is located in: " << database_path.string() << endl;
            
    delete db;
}